summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/routes/comics/[id]/+page.svelte
diff options
context:
space:
mode:
authorWolfgang Müller2025-02-13 17:52:16 +0100
committerWolfgang Müller2025-02-13 17:52:16 +0100
commitdc4db405d2991d3ec6a114f3b08d3fccd057d3ee (patch)
tree2c620c9af2062ba09fa591f8b3ed961664adab58 /frontend/src/routes/comics/[id]/+page.svelte
parent4df870d793123be95c8af031a340a39b5b8402ac (diff)
downloadhircine-dc4db405d2991d3ec6a114f3b08d3fccd057d3ee.tar.gz
frontend: Migrate to Svelte 5
Diffstat (limited to 'frontend/src/routes/comics/[id]/+page.svelte')
-rw-r--r--frontend/src/routes/comics/[id]/+page.svelte177
1 files changed, 82 insertions, 95 deletions
diff --git a/frontend/src/routes/comics/[id]/+page.svelte b/frontend/src/routes/comics/[id]/+page.svelte
index cfc5840..48c588f 100644
--- a/frontend/src/routes/comics/[id]/+page.svelte
+++ b/frontend/src/routes/comics/[id]/+page.svelte
@@ -2,12 +2,14 @@
import { beforeNavigate } from '$app/navigation';
import { updateComics } from '$gql/Mutations';
import { comicQuery } from '$gql/Queries';
- import { comicEquals } from '$gql/Utils';
- import { UpdateMode, type FullComicFragment, type UpdateComicInput } from '$gql/graphql';
- import { initReaderContext } from '$lib/Reader';
- import { initScraperContext } from '$lib/Scraper';
- import { initSelectionContext } from '$lib/Selection';
- import { setTabContext } from '$lib/Tabs';
+ import { omitIdentifiers, type OmitIdentifiers } from '$gql/Utils';
+ import {
+ UpdateMode,
+ type FullComicFragment,
+ type PageFragment,
+ type UpdateComicInput
+ } from '$gql/graphql';
+ import { comicPending } from '$lib/Form';
import { toastFinally } from '$lib/Toasts';
import { preventOnPending } from '$lib/Utils';
import BookmarkButton from '$lib/components/BookmarkButton.svelte';
@@ -21,155 +23,140 @@
import ComicForm from '$lib/forms/ComicForm.svelte';
import Gallery from '$lib/gallery/Gallery.svelte';
import PageView from '$lib/reader/PageView.svelte';
- import Reader from '$lib/reader/Reader.svelte';
+ import Reader, { initReaderContext } from '$lib/reader/Reader.svelte';
import ComicScrapeForm from '$lib/scraper/ComicScrapeForm.svelte';
+ import { initScraperContext } from '$lib/scraper/Scraper.svelte';
+ import { initSelectionContext } from '$lib/selection/Selection.svelte';
import ComicDelete from '$lib/tabs/ComicDelete.svelte';
import ComicDetails from '$lib/tabs/ComicDetails.svelte';
import Tab from '$lib/tabs/Tab.svelte';
import Tabs from '$lib/tabs/Tabs.svelte';
import SelectionControls from '$lib/toolbar/SelectionControls.svelte';
import { getContextClient } from '@urql/svelte';
- import type { PageData } from './$types';
+ import { untrack } from 'svelte';
+ import type { PageProps } from './$types';
+ let { data }: PageProps = $props();
const client = getContextClient();
const reader = initReaderContext();
- const selection = initSelectionContext();
const scraper = initScraperContext();
- const tabContext = setTabContext({
- tabs: {
- details: { title: 'Details' },
- edit: { title: 'Edit' },
- scrape: { title: 'Scrape' },
- deletion: { title: 'Delete' }
- },
- current: 'details'
- });
+ const selection = initSelectionContext<PageFragment>('Page', (p) => p.path);
- export let data: PageData;
- $: result = comicQuery(client, { id: data.id });
-
- let comic: FullComicFragment;
- let original: Readonly<FullComicFragment>;
- let updatePartial = false;
-
- $: $result, update();
- $: pending = !comicEquals(comic, original);
- $: $tabContext.tabs.edit.badge = pending;
-
- function update() {
- if (!$result.stale && $result.data?.comic.__typename === 'FullComic') {
- original = $result.data.comic;
- if (updatePartial) {
- comic.pages = structuredClone(original.pages);
- comic.favourite = original.favourite;
- comic.bookmarked = original.bookmarked;
- comic.organized = original.organized;
- comic.updatedAt = original.updatedAt;
- updatePartial = false;
- } else {
- comic = structuredClone(original);
- }
-
- $reader.pages = original.pages;
- $selection.view = comic.pages;
- $scraper.selector = undefined;
- }
+ let comic: FullComicFragment | undefined = $state();
+ let input: OmitIdentifiers<FullComicFragment> | undefined = $state();
+ let updateInput = $state(true);
+
+ function invalidateInput() {
+ updateInput = true;
}
- function toggle(field: keyof Omit<UpdateComicInput, 'cover'>) {
- updateComics(client, { ids: comic.id, input: { [field]: !comic[field] } })
- .then(() => (updatePartial = true))
- .catch(toastFinally);
+ function submit(input: UpdateComicInput) {
+ updateComics(client, { ids: data.id, input }).then(invalidateInput).catch(toastFinally);
}
- function updateComic(event: CustomEvent<UpdateComicInput>) {
- updateComics(client, { ids: comic.id, input: event.detail }).catch(toastFinally);
+ function toggle(field: keyof Pick<UpdateComicInput, 'bookmarked' | 'favourite' | 'organized'>) {
+ if (!comic) return;
+ updateComics(client, { ids: data.id, input: { [field]: !comic[field] } }).catch(toastFinally);
}
- function updateCover(event: CustomEvent<number>) {
- updateComics(client, { ids: comic.id, input: { cover: { id: event.detail } } })
- .then(() => (updatePartial = true))
- .catch(toastFinally);
+ function updateCover(id: number) {
+ updateComics(client, { ids: data.id, input: { cover: { id } } }).catch(toastFinally);
}
function removePages() {
updateComics(client, {
- ids: comic.id,
- input: { pages: { ids: $selection.ids, options: { mode: UpdateMode.Remove } } }
+ ids: data.id,
+ input: { pages: { ids: selection.ids, options: { mode: UpdateMode.Remove } } }
})
- .then(() => {
- updatePartial = true;
- $selection = $selection.clear();
- })
+ .then(selection.clear)
.catch(toastFinally);
}
beforeNavigate((navigation) => preventOnPending(navigation, pending));
+ let result = $derived(comicQuery(client, { id: data.id }));
+ let pending = $derived(comicPending(comic, input));
+
+ $effect(() => {
+ if (!$result.stale) {
+ untrack(() => {
+ if ($result.data?.comic.__typename === 'FullComic') {
+ comic = $result.data.comic;
+
+ if (updateInput) {
+ input = omitIdentifiers($result.data.comic);
+ updateInput = false;
+ }
+
+ reader.pages = comic.pages;
+ selection.view = comic.pages;
+ scraper.reset();
+ }
+ });
+ }
+ });
</script>
-<Head section="Comic" title={original?.title} />
+<Head section="Comic" title={comic?.title} />
-{#if comic}
+{#if comic && input}
<Grid>
<header>
<Titlebar
- title={original.title}
- subtitle={original.originalTitle}
- bind:favourite={comic.favourite}
- on:favourite={() => toggle('favourite')}
+ title={comic.title}
+ subtitle={comic.originalTitle}
+ favourite={comic.favourite}
+ onfavourite={() => toggle('favourite')}
/>
</header>
<aside>
- <Tabs>
- <Tab id="details">
- <ComicDetails comic={original} />
+ <Tabs badges={{ edit: pending }}>
+ <Tab initial id="details" title="Details">
+ <ComicDetails {comic} />
</Tab>
- <Tab id="edit">
+ <Tab id="edit" title="Edit">
<div class="flex flex-col gap-4">
<div class="flex gap-2 text-sm">
<SelectionControls page>
- <RemovePageButton on:click={removePages} />
+ <RemovePageButton onclick={removePages} />
</SelectionControls>
- <div class="grow" />
- <BookmarkButton bookmarked={comic.bookmarked} on:click={() => toggle('bookmarked')} />
- <OrganizedButton organized={comic.organized} on:click={() => toggle('organized')} />
+ <div class="grow"></div>
+ <BookmarkButton bookmarked={comic.bookmarked} onclick={() => toggle('bookmarked')} />
+ <OrganizedButton organized={comic.organized} onclick={() => toggle('organized')} />
</div>
- <ComicForm bind:comic on:submit={updateComic}>
+ <ComicForm bind:input {submit}>
<div class="flex gap-2">
- <div class="grow" />
- <SubmitButton active={pending} />
+ <div class="grow"></div>
+ <SubmitButton {pending} />
</div>
</ComicForm>
</div>
</Tab>
- <Tab id="scrape">
- <ComicScrapeForm {comic} />
+ <Tab id="scrape" title="Scrape">
+ <ComicScrapeForm {comic} onupsert={invalidateInput} />
</Tab>
- <Tab id="deletion">
+ <Tab id="deletion" title="Delete">
<ComicDelete {comic} />
</Tab>
</Tabs>
</aside>
<main class="overflow-auto">
- <Gallery
- pages={comic.pages}
- on:open={(e) => ($reader = $reader.open(e.detail))}
- on:cover={updateCover}
- />
+ <Gallery pages={comic.pages} open={reader.open} {updateCover} />
</main>
</Grid>
<Reader>
<PageView layout={comic.layout} direction={comic.direction} />
- <svelte:fragment slot="sidebar">
- <ComicForm bind:comic on:submit={updateComic}>
- <div class="flex justify-end gap-2">
- <SubmitButton active={pending} />
- </div>
- </ComicForm>
- </svelte:fragment>
+ {#snippet sidebar()}
+ {#if input}
+ <ComicForm bind:input {submit}>
+ <div class="flex justify-end gap-2">
+ <SubmitButton {pending} />
+ </div>
+ </ComicForm>
+ {/if}
+ {/snippet}
</Reader>
{:else}
<Guard {result} />